#include "Cell.h"

#include "../../qt/Logger.h"

#include "CellController.h"
#include "PhysicsParameters.h"
#include "World.h"

/*
	Cell
*/

/*	public	*/

Cell** Cell::newCellCheckers(World& world, int xPosition, int yPosition, int width, int height, int side) {

	/* create the result array */
	Cell** cells = new Cell*[width * height];

	/* fill the array */
	int index = 0;
	for(int i = 0; i != width; ++i) {
		for(int j = 0; j != height; ++j) {
			/* cell position */
			int x = xPosition + i * side;
			int y = yPosition + j * side;

			/* create cell */
			cells[index] = newCellSquare(world, x, y, side);

			++index;
		}
	}
	assert(index == width * height);

	return cells;
}

Cell* Cell::newCellSquare(World& world, int xPosition, int yPosition, int side) {

	/* new cell */
	Cell* cell = new Cell(world, side * side);

	/* fill world tiles*/
	for(int i = 0; i != side; ++i) {
		for(int j = 0; j != side; ++j) {
			world.setTile(xPosition + i, yPosition + j, cell->getId());
		}
	}

	return cell;
}

Cell::Cell(World& world, int currentSize) :
	_world(world),
	_id(world.getNewCellId()),
	_controller(0),
	_size(currentSize),
	_targetSize(currentSize),
	_membraneRigidity(1.0),
	_adhesionKey(0),
	_adhesionLock(0) {
	world.addCell(this);
}

Cell::~Cell() {
	delete _controller;
}

void Cell::setController(CellController* controller) {
	delete _controller;
	_controller = controller;
}

CellId Cell::getId() const {
	return _id;
}

void Cell::update() {
	/* controller */
	if (_controller) {
		_controller->update();
	}
}

double Cell::getEnergy(PhysicsParameters const& parameters, int xPosition, int yPosition) const {

	/* size */
	double sizeDiff = _targetSize - _size;
	double sizeEnergy = parameters.getSizeEnergyCoef() * sizeDiff * sizeDiff;

	/* membrane */
	double membraneEnergy;
	if (_world.grid(xPosition, yPosition) == _id) {
		membraneEnergy = _getNeighbours(xPosition, yPosition);
	} else {
		membraneEnergy = 0.0;
//		membraneEnergy = - _getNeighbours(xPosition, yPosition);
	}
//	membraneEnergy = _getNeighbours(xPosition, yPosition);
	membraneEnergy *= parameters.getMembraneEnergyCoef() * _membraneRigidity;

//	/* adhesion */
//	double energyAdhesion = parameters.getEnergyCoefAdhesion() * _adhesionGetSum(xPosition, yPosition, _id);

//	Logger::debug(QString("=== Cell::getEnergy() : %1").arg(sizeEnergy + membraneEnergy));
	/* sum */
//	return energySize + energyAdhesion;
	return sizeEnergy + membraneEnergy;
}

double Cell::getEnergyIfAdded(PhysicsParameters const& parameters, int xPosition, int yPosition) const {

	/* size */
	double sizeDiff = _targetSize - (_size + 1);
	double sizeEnergy = parameters.getSizeEnergyCoef() * sizeDiff * sizeDiff;

	/* membrane */
//	double membraneEnergy = parameters.getMembraneEnergyCoef() * _membraneRigidity * _membraneGetSumOur(xPosition, yPosition);
	double membraneEnergy = parameters.getMembraneEnergyCoef() * _membraneRigidity * _getNeighbours(xPosition, yPosition);

//	/* adhesion */
//	double energyAdhesion = parameters.getEnergyCoefAdhesion() * _adhesionGetSum(xPosition, yPosition, _id);

//	Logger::debug(QString("\tmembraneEnergy : %1").arg(membraneEnergy));
//	Logger::debug(QString("=== Cell::getEnergyIfAdded() : %1").arg(sizeEnergy + membraneEnergy));
	/* sum */
//	return energySize + energyAdhesion;
	return sizeEnergy + membraneEnergy;
}

double Cell::getEnergyIfRemoved(PhysicsParameters const& parameters, int xPosition, int yPosition) const {

	/* size */
	double sizeDiff = _targetSize - (_size - 1);
	double sizeEnergy = parameters.getSizeEnergyCoef() * sizeDiff * sizeDiff;

	/* membrane */
	double membraneEnergy = parameters.getMembraneEnergyCoef() * _membraneRigidity * _getNeighbours(xPosition, yPosition);
//	double membraneEnergy = 0.0;

//	Logger::debug(QString("=== Cell::getEnergyIfRemoved() : %1").arg(sizeEnergy + membraneEnergy));
	/* sum */
	return sizeEnergy + membraneEnergy;
}

void Cell::shrink() {
	--_size;
	assert(_size >= 0);
	//FIX do something on cell death ?
}

void Cell::grow() {
	++_size;
}

void Cell::setTargetSize(int target) {
	_targetSize = target;
}

void Cell::setMembraneRigidity(double rigidity) {
	_membraneRigidity = rigidity;
}

void Cell::setAdhesionKeyLock(int key, int lock) {
	_adhesionKey  = key;
	_adhesionLock = lock;
}

double Cell::getAdhesionToSubstrate() const {

	/* size */
	int size = ADHESION_KEY_LOCK_SIZE / 2;

	/* sum */
	int sum = 0;

	int key  = _adhesionKey;
	int lock = _adhesionLock;
	for(int i = size; i != 0; --i) {
		sum += (key & 1) + (lock & 1);
		key  >>= 1;
		lock >>= 1;
	}

	return double(sum);
}

double Cell::getAdhesionToCell(Cell const& cell) const {
	/* keys/locks */
	int key1  = _adhesionKey;
	int lock1 = _adhesionLock;
	int key2  = cell._adhesionKey;
	int lock2 = cell._adhesionLock;

	/* sum */
	int sum = 0;

	for(int i = ADHESION_KEY_LOCK_SIZE; i != 0; --i) {
		sum += ((key1 & 1) ^ (lock2 & 1)) + ((key2 & 1) ^ (lock1 & 1));
		key1  >>= 1;
		lock1 >>= 1;
		key2  >>= 1;
		lock2 >>= 1;
	}

	return double(sum);
}

/*	private	*/

double Cell::_getNeighbours(int xPosition, int yPosition) const {
	CellId centralId = _id;

	/* sum */
	int sum = 0;

	if (_world.grid(xPosition    , yPosition - 1) == centralId) { ++sum; } /* N  */
	if (_world.grid(xPosition + 1, yPosition - 1) == centralId) { ++sum; } /* NW */
	if (_world.grid(xPosition + 1, yPosition    ) == centralId) { ++sum; } /* W  */
	if (_world.grid(xPosition + 1, yPosition + 1) == centralId) { ++sum; } /* SW */
	if (_world.grid(xPosition    , yPosition + 1) == centralId) { ++sum; } /* S  */
	if (_world.grid(xPosition - 1, yPosition + 1) == centralId) { ++sum; } /* SE */
	if (_world.grid(xPosition - 1, yPosition    ) == centralId) { ++sum; } /* E  */
	if (_world.grid(xPosition - 1, yPosition - 1) == centralId) { ++sum; } /* NE */

//	Logger::debug(QString("Cell::_getNeighbours(%1, %2) : %3").arg(xPosition).arg(yPosition).arg(4 - sum));
	return 4 - sum;
}

double Cell::_membraneGetSumOur(int xPosition, int yPosition) const {
      /* count the borders */
      int borderCount = 0;

      /* top    */
      if (_id != _world.grid(xPosition, yPosition - 1)) {
              ++borderCount;
      }

      /* right  */
      if (_id != _world.grid(xPosition + 1, yPosition)) {
              ++borderCount;
      }

      /* bottom */
      if (_id != _world.grid(xPosition, yPosition + 1)) {
              ++borderCount;
      }

      /* left   */
      if (_id != _world.grid(xPosition - 1, yPosition)) {
              ++borderCount;
      }

//      if (borderCount == 4) { //FIX
//              borderCount = 1000;
//      }

      return borderCount;
}

double Cell::_membraneGetSumOther(int xPosition, int yPosition) const {
	/* count the borders */
	int borderCount = 0;

	/* top    */
	if (_world.grid(xPosition, yPosition - 1) == _id) {
		++borderCount;
	}

	/* right  */
	if (_world.grid(xPosition + 1, yPosition) == _id) {
		++borderCount;
	}

	/* bottom */
	if (_world.grid(xPosition, yPosition + 1) == _id) {
		++borderCount;
	}

	/* left   */
	if (_world.grid(xPosition - 1, yPosition) == _id) {
		++borderCount;
	}

	return borderCount;
}

bool Cell::unitTest() {

	/* cell */

	//FIX test adhesion stuff
	return true;
}

